package common.project.epic.db;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Stack;

import android.content.Context;
import android.database.Cursor;
import android.util.Log;

public class EpicContentManipulation {
	public EpicDBAdapter dbHelper;
	public String user_selected_categories;
	public String[] msg_categories;
	public Cursor cursor;
	public static final String TX_PRIORITY = "tx_priority";
	
	public static final double EarthCircumference = 40075017.0;
	private static final double MAX_MSG_RATE = 5;
	private static final int MAX_COPIES = 20;
	private static final int MIN_COPIES = 5;
	private static final String tag = "Message priority";
	
	public EpicContentManipulation(Context mContext){
		dbHelper = new EpicDBAdapter(mContext);
	}
	
	// this function should return an array of entries (String msg_id_value, double tx_prority_value)
	public Stack<EpicMsgIdPriority> computeMsgPriority(
			// the first param is used to filter the table contents
			String your_user_id,
			// the second param is used to compute the message transmission priority
			String your_pref_categories,
			// the third and 4-th param are the actual position
			double lat_now, double lon_now) { 
		
		Stack<EpicMsgIdPriority> stackEpic = new Stack<EpicMsgIdPriority>();
		
		Date date = new Date();
		long t_now_in_seconds = date.getTime()/1000;
		
		EpicMsgIdPriority[] emp_array;
		EpicMsgIdPriority emp_tmp;
		dbHelper.open();
		cursor = dbHelper.getFilteredContentsByUsers(your_user_id);
		if(cursor.getCount()>0) {
			emp_array = new EpicMsgIdPriority[cursor.getCount()];
			double tx_priority_value;
			cursor.moveToFirst();
			for(int i=0;i<emp_array.length;i++) {
				emp_tmp = new EpicMsgIdPriority();
				emp_tmp.msg_id = cursor.getString(cursor.getColumnIndex(EpicDBAdapter.MSG_ID));
				
				// (# of matching categories)/(\sum_i(1(msg \in category_i)))
				int nomc = 0;
				int sum_i = 0;
				int[] msgCategories = new int[16];
				int startIndex = cursor.getColumnIndex(EpicDBAdapter.MSG_CATEGORY_GENERAL);
				msgCategories[0] =  cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.MSG_CATEGORY_GENERAL));
				for(int tmpIndex=startIndex+1; tmpIndex<16+startIndex; tmpIndex++) {
					msgCategories[tmpIndex-startIndex] =  cursor.getInt(tmpIndex);
				}
				int[] yourCategories = new int[16];
				for(int tmpIndex=0; tmpIndex<16; tmpIndex++) {
					if(your_pref_categories.charAt(tmpIndex)=='0') {
						yourCategories[tmpIndex] = 0;
					}
					else if(your_pref_categories.charAt(tmpIndex)=='1') {
						yourCategories[tmpIndex] = 1;
						sum_i++;
						if(msgCategories[tmpIndex]== 1) {
							nomc++;
						}
					}
					else if(your_pref_categories.charAt(tmpIndex)=='2') {
						yourCategories[tmpIndex] = 2;
					}
				}
				sum_i++;
				nomc++;
				double catPriority = nomc/sum_i;
				String msgcatstr = ""; 
				for(int ind=0; ind<msgCategories.length; ind++) {msgcatstr = msgcatstr + msgCategories[ind];}
				Log.d(tag, "msgCategories " + msgcatstr);
				Log.d(tag, "your_pref_categories " + your_pref_categories);
				Log.d(tag, "nomc, sum_i (both increased by 1) = " + nomc + ", " + sum_i);
				
				int tcNumber = cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.TC_NUMBER));
				// TODO IF(tcNumber equals MAX_COPIES) {message should be filtered}
				// TODO add the clause in the method dbHelper.getFilteredContentsByUsers(your_user_id)
				if(tcNumber>MAX_COPIES) {tcNumber = MAX_COPIES;}
				double disseminationPriority = ( (double)(1.0/2.0)*(double)(1.0 - 
						((double)(2.0*(double)tcNumber/(double)((double)MAX_COPIES/(double)MIN_COPIES)) - 1.0) - 
						(1.0/Math.PI) * Math.sin(((double)(2.0*(double)tcNumber/(double)((double)MAX_COPIES/(double)MIN_COPIES)) - 
						1.0)*Math.PI)) );
				Log.d(tag, "tcNumber, disseminationPriority = " + tcNumber + ", " + disseminationPriority);
				
				double myRate = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MY_RATE));
				double rxRate = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_COPY_RATE));
				double msgRate;
				double hopCount = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_HOP_COUNTER));
				if(hopCount==0) {
					// I'm the creator of the message
					msgRate = rxRate;
					Log.d(tag, "msgRate @ hop 0 = " + msgRate);
				}
				else {
					msgRate = (myRate + rxRate*hopCount)/(hopCount+1);
					Log.d(tag, "msgRate @ hop " + hopCount + " = " + msgRate);
				}
				double lambdaTime = (double)msgRate/(double)MAX_MSG_RATE;
				double lambdaGeo = (double)msgRate/(double)MAX_MSG_RATE + 1.0;
				
				long msg_expire_time = cursor.getLong(cursor.getColumnIndex(EpicDBAdapter.MSG_EXP_TIME)); // [seconds]
				long ltc_time = cursor.getLong(cursor.getColumnIndex(EpicDBAdapter.LTC_TIME)); // [seconds]
				double time_ratio = ((double)t_now_in_seconds - (double)ltc_time)/((double)msg_expire_time - (double)t_now_in_seconds);
				double timePriority = (1.0 - Math.exp(-(time_ratio))); // (1.0 - Math.exp(-(time_ratio*(double)lambdaTime)));
				Log.d(tag, "exp, ltc, ratio, timePriority = " + 
				msg_expire_time + ", " + ltc_time + ", " + time_ratio + ", " + timePriority);
				
				double msg_lat = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_LAT));
				double msg_lon = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_LON));
				// recall that msg_geo_span is in [tens of meters]
				int msg_geo_span = cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_SPAN));
				long msg_geo_span_in_meters = msg_geo_span*10;
				double distanceInMeters = EarthCircumference/2.0;
				if(msg_lat==0.0 && msg_lon==0.0) {
					if(lat_now==0.0 && lon_now==0.0) {
						// message without geographic origin && actual position not available
						Log.d("EpicContentManipulation", "message without geographic origin && actual position not available");
						distanceInMeters = EarthCircumference;
					}
					else {
						// message without geographic origin && actual position available
						Log.d("EpicContentManipulation", "message without geographic origin && actual position available");
						distanceInMeters = EarthCircumference/2.0;
					}
				}
				else if(lat_now==0 && lon_now==0) {
					// message with geographic origin && actual position not available
					Log.d("EpicContentManipulation", "message with geographic origin && actual position not available");
					distanceInMeters = EarthCircumference/2.0;
				}
				else {
					// message with geographic origin && actual position available
					Log.d("EpicContentManipulation", "message with geographic origin && actual position available");
					android.location.Location myLocation = new android.location.Location("");
	    	        myLocation.setLatitude(lat_now);
	    	        myLocation.setLongitude(lon_now);
	    	        android.location.Location msgLocation = new android.location.Location("");
	    	        msgLocation.setLatitude(msg_lat);
	    	        msgLocation.setLongitude(msg_lon);
	    	        distanceInMeters = (double)myLocation.distanceTo(msgLocation);
				}
				double dist_ratio = distanceInMeters/(double)msg_geo_span_in_meters;
				double distPriority = Math.exp(-(dist_ratio*dist_ratio)); // Math.exp(-(dist_ratio*dist_ratio)/(lambdaGeo*lambdaGeo));
				Log.d(tag, "ratio, distPriority = " + dist_ratio + ", " + distPriority);
				
				tx_priority_value = catPriority * timePriority * distPriority * disseminationPriority;
				Log.d(tag, "priority = " + tx_priority_value);
				emp_tmp.priority = tx_priority_value; 
				
				emp_array[i] = emp_tmp;
				cursor.moveToNext();
			}
			for(int i=0;i<emp_array.length;i++) {
				Log.d(tag, "emp_array befor sorting (priority) = " + emp_array[i].priority);
			}
			Arrays.sort(emp_array);
			Log.d(tag, "emp_array[0] after sorting (priority) = " + emp_array[0].priority);
			for(EpicMsgIdPriority element : emp_array){
				Log.d(tag, "push(element) = " + element.priority);
				stackEpic.push(element);
			}
		}
		else {
			emp_array = null;
		}
		dbHelper.close();
		return stackEpic;
	}	
	
	
	public void computeQuarantineSpace(double lat_now, double lon_now) { 
		Stack<EpicMsgIdPriority> stackEpic = new Stack<EpicMsgIdPriority>();
		Date date = new Date();
		long t_now_in_seconds = date.getTime()/1000;
		long quarantineSpaceUsed = 0;
		
		EpicMsgIdPriority[] emp_array;
		EpicMsgIdPriority emp_tmp;
		dbHelper.open();
		cursor = dbHelper.getContents("quarantine");		
		if(cursor.getCount()>0) {
			emp_array = new EpicMsgIdPriority[cursor.getCount()];
			double tx_priority_value;
			cursor.moveToFirst();
			for(int i=0;i<emp_array.length;i++) {
				emp_tmp = new EpicMsgIdPriority();
				emp_tmp.msg_id = cursor.getString(cursor.getColumnIndex(EpicDBAdapter.MSG_ID));
				
				int tcNumber = cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.TC_NUMBER));
				// TODO IF(tcNumber equals MAX_COPIES) {message should be filtered}
				// TODO add the clause in the method dbHelper.getFilteredContentsByUsers(your_user_id)
				if(tcNumber>MAX_COPIES) {tcNumber = MAX_COPIES;}
				double disseminationPriority = ( (double)(1.0/2.0)*(double)(1.0 - 
						((double)(2.0*(double)tcNumber/(double)((double)MAX_COPIES/(double)MIN_COPIES)) - 1.0) - 
						(1.0/Math.PI) * Math.sin(((double)(2.0*(double)tcNumber/(double)((double)MAX_COPIES/(double)MIN_COPIES)) - 
						1.0)*Math.PI)) );
				Log.d(tag, "tcNumber, disseminationPriority = " + tcNumber + ", " + disseminationPriority);
				
				double myRate = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MY_RATE));
				double rxRate = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_COPY_RATE));
				double msgRate;
				double hopCount = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_HOP_COUNTER));
				if(hopCount==0) {
					// I'm the creator of the message
					msgRate = rxRate;
					Log.d(tag, "msgRate @ hop 0 = " + msgRate);
				}
				else {
					msgRate = (myRate + rxRate*hopCount)/(hopCount+1);
					Log.d(tag, "msgRate @ hop " + hopCount + " = " + msgRate);
				}
				double lambdaTime = (double)msgRate/(double)MAX_MSG_RATE;
				double lambdaGeo = (double)msgRate/(double)MAX_MSG_RATE + 1.0;
				
				long msg_expire_time = cursor.getLong(cursor.getColumnIndex(EpicDBAdapter.MSG_EXP_TIME)); // [seconds]
				long ltc_time = cursor.getLong(cursor.getColumnIndex(EpicDBAdapter.LTC_TIME)); // [seconds]
				double time_ratio = ((double)t_now_in_seconds - (double)ltc_time)/((double)msg_expire_time - (double)t_now_in_seconds);
				double timePriority = (1.0 - Math.exp(-(time_ratio))); // (1.0 - Math.exp(-(time_ratio*(double)lambdaTime)));
				Log.d(tag, "exp, ltc, ratio, timePriority = " + 
				msg_expire_time + ", " + ltc_time + ", " + time_ratio + ", " + timePriority);
				
				double msg_lat = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_LAT));
				double msg_lon = cursor.getDouble(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_LON));
				// recall that msg_geo_span is in [tens of meters]
				int msg_geo_span = cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.MSG_GEO_SPAN));
				long msg_geo_span_in_meters = msg_geo_span*10;
				double distanceInMeters = EarthCircumference/2.0;
				if(msg_lat==0.0 && msg_lon==0.0) {
					if(lat_now==0.0 && lon_now==0.0) {
						// message without geographic origin && actual position not available
						Log.d("EpicContentManipulation", "message without geographic origin && actual position not available");
						distanceInMeters = EarthCircumference;
					}
					else {
						// message without geographic origin && actual position available
						Log.d("EpicContentManipulation", "message without geographic origin && actual position available");
						distanceInMeters = EarthCircumference/2.0;
					}
				}
				else if(lat_now==0 && lon_now==0) {
					// message with geographic origin && actual position not available
					Log.d("EpicContentManipulation", "message with geographic origin && actual position not available");
					distanceInMeters = EarthCircumference/2.0;
				}
				else {
					// message with geographic origin && actual position available
					Log.d("EpicContentManipulation", "message with geographic origin && actual position available");
					android.location.Location myLocation = new android.location.Location("");
	    	        myLocation.setLatitude(lat_now);
	    	        myLocation.setLongitude(lon_now);
	    	        android.location.Location msgLocation = new android.location.Location("");
	    	        msgLocation.setLatitude(msg_lat);
	    	        msgLocation.setLongitude(msg_lon);
	    	        distanceInMeters = (double)myLocation.distanceTo(msgLocation);
				}
				double dist_ratio = distanceInMeters/(double)msg_geo_span_in_meters;
				double distPriority = Math.exp(-(dist_ratio*dist_ratio)); // Math.exp(-(dist_ratio*dist_ratio)/(lambdaGeo*lambdaGeo));
				Log.d(tag, "ratio, distPriority = " + dist_ratio + ", " + distPriority);
				
				tx_priority_value = timePriority * distPriority * disseminationPriority;
				Log.d(tag, "priority = " + tx_priority_value);
				emp_tmp.priority = 1/tx_priority_value; 
				
				quarantineSpaceUsed = quarantineSpaceUsed + cursor.getInt(cursor.getColumnIndex(EpicDBAdapter.MSG_SIZE));
				
				emp_array[i] = emp_tmp;
				cursor.moveToNext();
			}
			for(int i=0;i<emp_array.length;i++) {
				Log.d(tag, "quarantine drop: befor sorting (priority^-1) = " + emp_array[i].priority);
			}
			Arrays.sort(emp_array);
			Log.d(tag, "quarantine drop: after sorting (priority^-1) = " + emp_array[0].priority);
			for(EpicMsgIdPriority element : emp_array){
				Log.d(tag, "push(element) = " + element.priority);
				stackEpic.push(element);
			}
			while(quarantineSpaceUsed>CurrentUser.getQuarantineValue()) {
				int sizeTmp = 0;
				EpicMsgIdPriority emip = stackEpic.pop();
				Cursor mCursor = dbHelper.getContent(emip.msg_id);
				sizeTmp = mCursor.getInt(mCursor.getColumnIndex(EpicDBAdapter.MSG_SIZE));
				if(dbHelper.removeContent(emip.msg_id)) {
					Log.d(tag, "drop quarantine message");
					quarantineSpaceUsed = quarantineSpaceUsed - sizeTmp; 
				}
				else {Log.d(tag, "Not able to drop quarantine message!!!");}
			}
		}
		else {
			emp_array = null;
		}
		dbHelper.close();
		return;
	}
}